//STL
#include <stdexcept>
#include <iostream>
#include <vector>
#include <algorithm>
#include <time.h>
#include <math.h>

//Game
#include "Billiards.h"
#include "Graphics/Engine.h"
#include "Game/Factory.h"

using namespace Math;

Billiards* Billiards::_instance=NULL;

Billiards::Billiards()
{
	_scene=NULL;
	_poolTable=NULL;
	_root=Point(0.0f,0.0f,0.0f);
	_8Ball=NULL;
	_cueBall=NULL;
	_fps=60;
	_mspf=1000/_fps;
	cue=new Vector(0.0f,0.0f,-1.0f);
	speed=30;
}

Billiards* Billiards::instance()
{
	if (!_instance)
		_instance=new Billiards();

	return _instance;
}

void Billiards::_tick(int userdata)
{
	if (userdata<1)
		return;

	//Start from 1 to skip the cue ball
	/*for (unsigned int i=1;i<_instance->_balls.size();i++)
	{
		Game::Ball& b=*_instance->_balls.at(i);

		if (*_instance->_cueBall % b)
		{
			std::cout << "Cue ball collided with Ball #" << b.number() << std::endl ;
			b.setDirection(Vector(_instance->_cueBall->center(),b.center()));
			_instance->_cueBall->setDirection(Vector(b.center(),_instance->_cueBall->center()));

			b.setSpeed(_instance->_cueBall->speed());
			b.setAcceleration(_instance->_cueBall->acceleration()*0.999f);
			b.rotateX(-2.0f);

			_instance->_cueBall->setSpeed(_instance->_cueBall->speed()*0.75f);
			_instance->_cueBall->setAcceleration(_instance->_cueBall->acceleration()*0.99f);
		}
	}*/

	for (unsigned int i=0;i<_instance->_balls.size()-1;++i)
	{
		Game::Ball& b1=*_instance->_balls.at(i);

		for (unsigned int j=i+1;j<_instance->_balls.size();++j)
		{
			Game::Ball& b2=*_instance->_balls.at(j);

			float collision_amount=b1%b2;
			//Test if the two balls collide
			if (collision_amount)
			{
				//std::cout << "Ball #" << b1.number() << " collided with Ball #" << b2.number() << " for " << collision_amount << " units." << std::endl ;

				b2.setDirection(Vector(b1.center(),b2.center()));
				b1.setDirection(Vector(b2.center(),b1.center()));

				b2.setSpeed(b1.speed()*0.99f);
				b2.setAcceleration(b1.acceleration()*0.999f);
				b2.setRotation(*b1.rotation());

				b1.setSpeed(b1.speed()*0.75f);
				b1.setAcceleration(b1.acceleration()*0.99f);
				//b1.rotation()->setAngle(b1.rotation()->angle()*-1.0f);

				//Separate the two balls because they must have intersected
				b2.move(b2.direction().unit()*collision_amount*3.0f);
				b2.rotateX(1.0f);
			}
		}

		Vector* newdir=_instance->_poolTable->collide(b1);

		if (newdir!=NULL)
		{
			b1.setDirection(*newdir);
			b1.setSpeed(b1.speed()*0.9f);
		}
	}
/*	if (_instance->_cueBall->speed()>0.0f)
	{
		Vector amount=_instance->_cueBall->travel();
		std::cout << "Move by: " << amount << std::endl;
		_instance->_cueBall->move(amount);
		_instance->_cueBall->rotateX(-20.0f*_instance->_fps*_instance->_cueBall->speed());
	}
*/

	for (unsigned int i=0;i<_instance->_balls.size();i++)
	{
		Game::Ball& b=*_instance->_balls.at(i);

		if (b.speed()>0.0f)
		{
			Vector amount=b.travel();
			//std::cout << "Move ball #" << b.number()<< " by " << amount << std::endl;
			b.move(amount);
			b.rotateX(-20.0f*_instance->_fps*b.speed());
		}
	}

	glutPostRedisplay();
	glutTimerFunc(_instance->_mspf,_tick,--userdata);
}

void Billiards::animate(float seconds)
{
	//See if we 're going to animate just once
	if (seconds<=(float)_mspf/1000.0f)
		_tick(1);
	else
		//Schedule a timer
		glutTimerFunc(_mspf,_tick,(int)ceil(seconds*_fps));
}

void Billiards::start()
{
	_cueBall->setDirection(*cue);
	_cueBall->setSpeed(speed*_cueBall->radius()/(float)_fps);
	_cueBall->setAcceleration(0.999f);
	_cueBall->rotateX(_cueBall->speed());
	animate(30.0f);
}

void Billiards::createGame()
{
	//Create the assets
	createAssets();

	//Create the Scene
	createScene();

	//Set the main scene
	Graphics::Engine::instance()->setScene(_scene);
}

void Billiards::createAssets()
{
	createBalls();

	//Create the pool table
	_poolTable=Game::Factory::instance()->createPoolTable();

}

void Billiards::createBalls()
{
	for (int i=0;i<Billiards::MAX_BALLS;++i)
	{
		Game::Ball* b=Game::Factory::instance()->createBall(i);

		if (i==0)
			_cueBall=b;
		else
		if (i==8)
			_8Ball=b;

		_balls.push_back(b);
	}
}

void Billiards::createScene()
{
	_scene=new Graphics::Drawable();

	//Add the pool table to the scene
	_scene->add(_poolTable);

	_poolTable->move(Vector(0.0f,-Game::Factory::instance()->ballRadius(),0.0f));
	//Replace table color
	_poolTable->texture()->setModulate(false);

	_root=Point(0.0f,0.0f, -_poolTable->size().height()/6.0f);

	//Add the balls to the scene
	std::vector<Game::Ball*>::iterator it;
	for (it=_balls.begin();it!=_balls.end();it++)
		_scene->add(*it);

	//Arrange the balls in a triangular shape
	arrangeBalls();

	//Put the cue ball in place
	resetCueBall();
}

void Billiards::arrangeBalls()
{
	//Make sure there's at least 1 ball in the set
	if ((_balls.size()<1) || (Billiards::MAX_BALLS<1))
		throw new std::logic_error("Cannot create ball arrangement: no pool balls available.");

	//The coordinate to be calculated
	Point pos;

	//Number of balls/row
	int cols=0;
	//State variables
	//int row=0;
	//int col=0;

	//Where the row starts. First row starts at root.
	Point rowStart(_root);

	//Loop var starts at 2, to ignore the cue ball
	unsigned int i=1;

	//Assume all balls have the same radius. Cue ball's radius is used.
	float radius=_balls.at(0)->radius();
	//Spacing around each ball (1% of the diameter)
	float spacing=(radius*2.0f)/100.0f;

	srand(static_cast<unsigned int>(time(NULL)));
	//Randomize the balls
	std::random_shuffle(++_balls.begin(),_balls.end());
	//Make sure ball #8 is in the middle
	//std::swap(_balls[5],*std::find(_balls.begin(),_balls.end(),_8Ball));
	//std::swap(_balls.begin()+5,std::find(_balls.begin(),_balls.end(),_8Ball));
	
	//Try to find the 8ball in the list
	std::vector<Game::Ball*>::iterator it=std::find(_balls.begin(),_balls.end(),_8Ball);

	if (it!=_balls.end())
		std::swap(_balls[5],*it);

	//Loop through all available game balls
	while (i<_balls.size())
	{
		int col=0;
		cols++;
		//The X component moves "left" by Radius+Spacing, except for the first ball
		if (cols>1)
			rowStart.setX(rowStart.x()-(radius+spacing));
		//Each row moves the Z coordinate by an equivalent of sqrt(4*radius^2 - radius^2) (using Pythagoras)
		//hence, radius*sqrt(3). We then consider the spacing as part of the radius.
		rowStart.setZ(rowStart.z()-(radius+spacing)*SQRT3);

		//First ball starts where the row starts
		pos=rowStart;

		//Loop for 'cols' number of times or until we run out of game balls
		while ((col++<cols) && (i<_balls.size()))
		{
			//Get a pointer to the ball
			Game::Ball* b=_balls.at(i);

			//Move the ball where it's supposed to be
			b->move(pos);
			//Rotate it accordingly
			b->rotateX(60.0f);
			//Add the ball to the scene
			_scene->add(b);
			i++;

			//Determine where to place the next ball
			pos.setX(pos.x()+2*radius+spacing);
		}
	}
}

void Billiards::resetCueBall()
{
	if (!_cueBall)
	{
		std::cerr << "Warning: Cannot reset cue ball, because no cue ball defined." << std::endl;
		return;
	}

	if (!_poolTable)
	{
		std::cerr << "Warning: Cannot reset the cue ball, because no pool table defined." << std::endl;
	}

	Point pos(0.0f,
						_poolTable->position()->y()+_cueBall->radius(),
						0.25f*_poolTable->size().height());

	_cueBall->setPosition(pos);
	_cueBall->rotateX(120.0f);
}
